﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.templates;
using l1cs.server.server.types;
using l1cs.server.model.gametime;
using l1cs.server.model.Instance;
using l1cs.server.datatables;
using System.Threading;

namespace l1cs.server.model
{
    public class L1Spawn : L1GameTimeAdapter
    {
        private static Logger _log = Logger.getLogger( typeof( L1Spawn ).FullName );
        private L1Npc _template;

        private int _id; // just to find this in the spawn table
        private String _location;
        private int _maximumCount;
        private int _npcid;
        private int _groupId;
        private int _locx;
        private int _locy;
        private int _randomx;
        private int _randomy;
        private int _locx1;
        private int _locy1;
        private int _locx2;
        private int _locy2;
        private int _heading;
        private int _minRespawnDelay;
        private int _maxRespawnDelay;
        private short _mapid;
        private bool _respaenScreen;
        private int _movementDistance;
        private bool _rest;
        private int _spawnType;
        private int _delayInterval;
        private L1SpawnTime _time;
        private Dictionary<int, Point> _homePoint = null; // initでspawnした個々のオブジェクトのホームポイント
        private List<L1NpcInstance> _mobs = new List<L1NpcInstance>();

        private static Random _random = new Random();

        private String _name;

        //private class SpawnTask implements Runnable {
        private int _spawnNumber;
        private int _objectId;

        //    private SpawnTask(int spawnNumber, int objectId) {
        //        _spawnNumber = spawnNumber;
        //        _objectId = objectId;
        //    }

        //    @Override
        public void run( Object stat )
        {
            doSpawn( _spawnNumber, _objectId );
        }
        //}

        public L1Spawn( L1Npc mobTemplate )
        {
            _template = mobTemplate;
        }

        public String getName()
        {
            return _name;
        }

        public void setName( String name )
        {
            _name = name;
        }

        public short getMapId()
        {
            return _mapid;
        }

        public void setMapId( short _mapid )
        {
            this._mapid = _mapid;
        }

        public bool isRespawnScreen()
        {
            return _respaenScreen;
        }

        public void setRespawnScreen( bool flag )
        {
            _respaenScreen = flag;
        }

        public int getMovementDistance()
        {
            return _movementDistance;
        }

        public void setMovementDistance( int i )
        {
            _movementDistance = i;
        }

        public int getAmount()
        {
            return _maximumCount;
        }

        public int getGroupId()
        {
            return _groupId;
        }

        public int getId()
        {
            return _id;
        }

        public String getLocation()
        {
            return _location;
        }

        public int getLocX()
        {
            return _locx;
        }

        public int getLocY()
        {
            return _locy;
        }

        public int getNpcId()
        {
            return _npcid;
        }

        public int getHeading()
        {
            return _heading;
        }

        public int getRandomx()
        {
            return _randomx;
        }

        public int getRandomy()
        {
            return _randomy;
        }

        public int getLocX1()
        {
            return _locx1;
        }

        public int getLocY1()
        {
            return _locy1;
        }

        public int getLocX2()
        {
            return _locx2;
        }

        public int getLocY2()
        {
            return _locy2;
        }

        public int getMinRespawnDelay()
        {
            return _minRespawnDelay;
        }

        public int getMaxRespawnDelay()
        {
            return _maxRespawnDelay;
        }

        public void setAmount( int amount )
        {
            _maximumCount = amount;
        }

        public void setId( int id )
        {
            _id = id;
        }

        public void setGroupId( int i )
        {
            _groupId = i;
        }

        public void setLocation( String location )
        {
            _location = location;
        }

        public void setLocX( int locx )
        {
            _locx = locx;
        }

        public void setLocY( int locy )
        {
            _locy = locy;
        }

        public void setNpcid( int npcid )
        {
            _npcid = npcid;
        }

        public void setHeading( int heading )
        {
            _heading = heading;
        }

        public void setRandomx( int randomx )
        {
            _randomx = randomx;
        }

        public void setRandomy( int randomy )
        {
            _randomy = randomy;
        }

        public void setLocX1( int locx1 )
        {
            _locx1 = locx1;
        }

        public void setLocY1( int locy1 )
        {
            _locy1 = locy1;
        }

        public void setLocX2( int locx2 )
        {
            _locx2 = locx2;
        }

        public void setLocY2( int locy2 )
        {
            _locy2 = locy2;
        }

        public void setMinRespawnDelay( int i )
        {
            _minRespawnDelay = i;
        }

        public void setMaxRespawnDelay( int i )
        {
            _maxRespawnDelay = i;
        }

        private int calcRespawnDelay()
        {
            int respawnDelay = _minRespawnDelay * 1000;
            if ( _delayInterval > 0 )
            {
                respawnDelay += _random.Next( _delayInterval ) * 1000;
            }
            L1GameTime currentTime = L1GameTimeClock.getInstance().currentTime();
            //if (_time != null && !_time.getTimePeriod().includes(currentTime))
            //{ // 指定時間外なら指定時間までの時間を足す
            //    long diff = (_time.getTimeStart().Milliseconds - currentTime.toTime().getTime());
            //    if (diff < 0)
            //    {
            //        diff += 24 * 1000L * 3600L;
            //    }
            //    diff /= 6; // real time to game time
            //    respawnDelay = (int)diff;
            //}
            return respawnDelay;
        }

        private Timer tmr;
        /// <summary>
        /// SpawnTaskを起動する。
        /// </summary>
        /// <param name="spawnNumber">L1Spawnで管理されている番号。ホームポイントが無ければ何を指定しても良い。</param>
        /// <param name="objectId"></param>
        public void executeSpawnTask( int spawnNumber, int objectId )
        {
            //SpawnTask task = new SpawnTask(spawnNumber, objectId);
            //GeneralThreadPool.getInstance().schedule(task, calcRespawnDelay());
            tmr =
                new System.Threading.Timer(
                new System.Threading.TimerCallback( run ),
                null, 0, calcRespawnDelay() );
        }

        private bool _initSpawn = false;

        private bool _spawnHomePoint;

        public void init()
        {
            if ( _time != null && _time.isDeleteAtEndTime() )
            {
                // 時間外削除が指定されているなら、時間経過の通知を受ける。
                L1GameTimeClock.getInstance().addListener( this );
            }
            _delayInterval = _maxRespawnDelay - _minRespawnDelay;
            _initSpawn = true;
            // ホームポイントを持たせるか
            if ( Config.SPAWN_HOME_POINT
                    && Config.SPAWN_HOME_POINT_COUNT <= getAmount()
                    && Config.SPAWN_HOME_POINT_DELAY >= getMinRespawnDelay()
                    && isAreaSpawn() )
            {
                _spawnHomePoint = true;
                _homePoint = new Dictionary<int, Point>();
            }

            int spawnNum = 0;
            while ( spawnNum < _maximumCount )
            {
                // spawnNumは1～maxmumCountまで
                doSpawn( ++spawnNum );
            }
            _initSpawn = false;
        }

        /// <summary>
        /// ホームポイントがある場合は、spawnNumberを基にspawnする。 それ以外の場合は、spawnNumberは未使用。
        /// </summary>
        /// <param name="spawnNumber"></param>
        protected void doSpawn( int spawnNumber )
        {
            // 初期配置
            // 指定時間外であれば、次spawnを予約して終わる。
            if ( _time != null
                    && !_time.getTimePeriod().includes(
                            L1GameTimeClock.getInstance().currentTime() ) )
            {
                executeSpawnTask( spawnNumber, 0 );
                return;
            }
            doSpawn( spawnNumber, 0 );
        }

        protected void doSpawn( int spawnNumber, int objectId )
        {
            // 再出現
            L1NpcInstance mob = null;
            int newlocx = getLocX();
            int newlocy = getLocY();
            int tryCount = 0;

            mob = NpcTable.getInstance().newNpcInstance( _template );
            lock ( _mobs )
            {
                _mobs.Add( mob );
            }
            if ( objectId == 0 )
            {
                mob.setId( IdFactory.getInstance().nextId() );
            }
            else
            {
                mob.setId( objectId ); // オブジェクトID再利用
            }

            if ( 0 <= getHeading() && getHeading() <= 7 )
            {
                mob.setHeading( getHeading() );
            }
            else
            {
                // heading値が正しくない
                mob.setHeading( 5 );
            }

            int npcId = mob.getNpcTemplate().get_npcId();
            if ( npcId == 45488 && getMapId() == 9 )
            { // カスパー
                mob.setMap( (short)( getMapId() + _random.Next( 2 ) ) );
            }
            else if ( npcId == 45601 && getMapId() == 11 )
            { // デスナイト
                mob.setMap( (short)( getMapId() + _random.Next( 3 ) ) );
            }
            else
            {
                mob.setMap( getMapId() );
            }
            mob.setMovementDistance( getMovementDistance() );
            mob.setRest( isRest() );
            while ( tryCount <= 50 )
            {
                if ( getSpawnType() == SPAWN_TYPE_PC_AROUND )
                {
                    // PC周辺に湧くタイプ
                    if ( !_initSpawn )
                    { // 初期配置では無条件に通常spawn
                        List<L1PcInstance> players = new List<L1PcInstance>();
                        foreach ( L1PcInstance pc in L1World.getInstance().getAllPlayers() )
                        {
                            if ( getMapId() == pc.getMapId() )
                            {
                                players.Add( pc );
                            }
                        }
                        if ( players.Count > 0 )
                        {
                            L1PcInstance pc = players[_random.Next( players.Count )];
                            L1Location loc = pc.getLocation().randomLocation( PC_AROUND_DISTANCE, false );
                            newlocx = loc.getX();
                            newlocy = loc.getY();
                            break;
                        }
                    }
                }
                else
                {
                    // フロアにPCがいなければ通常の出現方法
                    if ( isAreaSpawn() )
                    { // 座標が範囲指定されている場合
                        Point pt = null;
                        if ( _homePoint != null )
                        {
                            _homePoint.TryGetValue( spawnNumber, out pt );
                        }
                        if ( _spawnHomePoint && null != pt )
                        { // ホームポイントを元に再出現させる場合
                            L1Location loc = new L1Location( pt, getMapId() ).randomLocation(
                                            Config.SPAWN_HOME_POINT_RANGE, false );
                            newlocx = loc.getX();
                            newlocy = loc.getY();
                        }
                        else
                        {
                            int rangeX = getLocX2() - getLocX1();
                            int rangeY = getLocY2() - getLocY1();
                            newlocx = _random.Next( rangeX ) + getLocX1();
                            newlocy = _random.Next( rangeY ) + getLocY1();
                        }
                        if ( tryCount > 49 )
                        { // 出現位置が決まらない時はlocx,locyの値
                            newlocx = getLocX();
                            newlocy = getLocY();
                        }
                    }
                    else if ( isRandomSpawn() )
                    { // 座標のランダム値が指定されている場合
                        newlocx = ( getLocX() + ( (int)( _random.Next( 1 ) * getRandomx() ) - (int)( _random.Next( 1 ) * getRandomx() ) ) );
                        newlocy = ( getLocY() + ( (int)( _random.Next( 1 ) * getRandomy() ) - (int)( _random.Next( 1 ) * getRandomy() ) ) );
                    }
                    else
                    { // どちらも指定されていない場合
                        newlocx = getLocX();
                        newlocy = getLocY();
                    }
                }
                mob.setX( newlocx );
                mob.setHomeX( newlocx );
                mob.setY( newlocy );
                mob.setHomeY( newlocy );

                if ( mob.getMap().isInMap( mob.getLocation() )
                        && mob.getMap().isPassable( mob.getLocation() ) )
                {
                    if ( mob is L1MonsterInstance )
                    {
                        if ( isRespawnScreen() )
                        {
                            break;
                        }
                        L1MonsterInstance mobtemp = (L1MonsterInstance)mob;
                        if ( L1World.getInstance().getVisiblePlayer( mobtemp ).Count == 0 )
                        {
                            break;
                        }
                        // 画面内にPCが居て出現できない場合は、3秒後にスケジューリングしてやり直し
                        //SpawnTask task = new SpawnTask(spawnNumber, mob.getId());
                        //GeneralThreadPool.getInstance().schedule(task, 3000L);
                        tmr.Change( 3000, calcRespawnDelay() );
                        return;
                    }
                }
                tryCount++;
            }
            if ( mob is L1MonsterInstance )
            {
                ( (L1MonsterInstance)mob ).initHide();
            }

            mob.setSpawn( this );
            mob.setreSpawn( true );
            mob.setSpawnNumber( spawnNumber ); // L1Spawnでの管理番号(ホームポイントに使用)
            if ( _initSpawn && _spawnHomePoint )
            { // 初期配置でホームポイントを設定
                Point pt = new Point( mob.getX(), mob.getY() );
                _homePoint[spawnNumber] = pt; // ここで保存したpointを再出現時に使う
            }

            if ( mob is L1MonsterInstance )
            {
                if ( mob.getMapId() == 666 )
                {
                    ( (L1MonsterInstance)mob ).set_storeDroped( true );
                }
            }
            if ( npcId == 45573 && mob.getMapId() == 2 )
            { // バフォメット
                foreach ( L1PcInstance pc in L1World.getInstance().getAllPlayers() )
                {
                    if ( pc.getMapId() == 2 )
                    {
                        L1Teleport.teleport( pc, 32664, 32797, (short)2, 0,
                                true );
                    }
                }
            }

            if ( npcId == 46142 && mob.getMapId() == 73
                    || npcId == 46141 && mob.getMapId() == 74 )
            {
                foreach ( L1PcInstance pc in L1World.getInstance().getAllPlayers() )
                {
                    if ( pc.getMapId() >= 72 && pc.getMapId() <= 74 )
                    {
                        L1Teleport.teleport( pc, 32840, 32833, (short)72,
                                pc.getHeading(), true );
                    }
                }
            }
            doCrystalCave( npcId );

            L1World.getInstance().storeObject( mob );
            L1World.getInstance().addVisibleObject( mob );

            if ( mob is L1MonsterInstance )
            {
                L1MonsterInstance mobtemp = (L1MonsterInstance)mob;
                if ( !_initSpawn && mobtemp.getHiddenStatus() == 0 )
                {
                    mobtemp.onNpcAI(); // モンスターのＡＩを開始
                }
            }
            if ( getGroupId() != 0 )
            {
                L1MobGroupSpawn.getInstance().doSpawn( mob, getGroupId(), isRespawnScreen(), _initSpawn );
            }
            mob.turnOnOffLight();
            mob.startChat( L1NpcInstance.CHAT_TIMING_APPEARANCE ); // チャット開始

        }

        public void setRest( bool flag )
        {
            _rest = flag;
        }

        public bool isRest()
        {
            return _rest;
        }

        private const int SPAWN_TYPE_NORMAL = 0;
        private const int SPAWN_TYPE_PC_AROUND = 1;

        private const int PC_AROUND_DISTANCE = 30;

        private int getSpawnType()
        {
            return _spawnType;
        }

        public void setSpawnType( int type )
        {
            _spawnType = type;
        }

        private bool isAreaSpawn()
        {
            return getLocX1() != 0 && getLocY1() != 0 && getLocX2() != 0 && getLocY2() != 0;
        }

        private bool isRandomSpawn()
        {
            return getRandomx() != 0 || getRandomy() != 0;
        }

        public L1SpawnTime getTime()
        {
            return _time;
        }

        public void setTime( L1SpawnTime time )
        {
            _time = time;
        }

        public void onMinuteChanged( L1GameTime time )
        {
            if ( _time.getTimePeriod().includes( time ) )
            {
                return;
            }
            lock ( _mobs )
            {
                if ( _mobs.Count == 0 )
                {
                    return;
                }
                // 指定時間外になっていれば削除
                foreach ( L1NpcInstance mob in _mobs )
                {
                    mob.setCurrentHpDirect( 0 );
                    mob.setDead( true );
                    mob.setStatus( ActionCodes.ACTION_Die );
                    mob.deleteMe();
                }
                _mobs.Clear();
            }
        }

        public static void doCrystalCave( int npcId )
        {
            int[] npcId2 = { 46143, 46144, 46145, 46146, 46147, 46148, 46149, 46150, 46151, 46152 };
            int[] doorId = { 5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010 };

            for ( int i = 0; i < npcId2.Length; i++ )
            {
                if ( npcId == npcId2[i] )
                {
                    closeDoorInCrystalCave( doorId[i] );
                }
            }
        }

        private static void closeDoorInCrystalCave( int doorId )
        {
            foreach ( L1Object l1object in L1World.getInstance().getObject() )
            {
                if ( l1object is L1DoorInstance )
                {
                    L1DoorInstance door = (L1DoorInstance)l1object;
                    if ( door.getDoorId() == doorId )
                    {
                        door.close();
                    }
                }
            }
        }
    }
}
